﻿﻿/* **********************************************************************************
 * Copyright (c) 2011 John Hughes
 *
 * j9Script is licenced under the Microsoft Reciprocal License (Ms-RL).
 *
 * Project Website: http://j9Script.codeplex.com/
 * **********************************************************************************/

using System;
using System.Collections.Specialized;
using System.Collections;
using System.Data;
using System.Reflection;
using System.Diagnostics;
using J9Script.Internal;

namespace J9Script
{
	// This module is composed of a Recursive-Decent parser.
	//
    // USAGE:  x = ScriptEvaluator.EvaluateNow("...")
	//
	// Precedence Rules:
	// 
	//   ( )                Expression                 (Highest Precedence)
	//   -, !               Unary
	//   functions()        Functions
	//   ++ --              Post Increment/Decrement
	//   new T(...)         Object Creation
	//   *, /, %            Multiplicative
	//   +, -               Additive       (+ is also used for String Concatenation)
	//   <<, >>             Bitwise Shift
	//   <, >, <=, >=       Relational
	//   ==, !=             Equality
	//   &                  Bitwise AND
	//   ^                  Bitwise exclusive OR
	//   |                  Bitwise inclusive OR
	//   &&                 Logical AND
	//   ||                 Logical OR
	//   =                  Assignment                 (Lowest Precedence)


    /// <summary>
    /// Provides a means to add custom global functions to the scripting language. It is recommended that new ScriptObject classes be created when applicable.
    /// </summary>
    /// <param name="functionname">The name of the global function used in the script.</param>
    /// <param name="args">The global function parameter values from the script.</param>
    /// <param name="result">The result of the function.</param>
    /// <returns>A value indicating if the call succeeded.</returns>
    public delegate ScriptObjectInvokeResult GlobalFunctionEvaluatorDelegate(string functionname, IScriptObject[] args, out IScriptObject result);

    public struct ScriptEvaluator
    {
        ScriptPosition _blockStartPosition;
        Token _token;
        ScriptVariableCollection _variables;
        GlobalFunctionEvaluatorDelegate _globalFunctionEvaluator;
        ScriptObjectTypeInfoCollection _objectInfos; // used for static object members and creating instances with the new keyword.
        ScriptFunctionInfoCollection _scriptFunctionInfos;
        bool _showDebug; // TODO: allow _showDebug  to be set to something

        ScriptParser _parser;

        SponsorshipManager _scriptObjectSponsorManager;

        public static IScriptObject EvaluateNow(string s)
        {
            try
            {
                return EvaluateNow(s, null, null, null);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public static IScriptObject EvaluateNow(string s, ScriptObjectTypeInfoCollection objectInfos)
        {
            try
            {
                ScriptEvaluator ee = new ScriptEvaluator();
                return ee.Evaluate(s, null, null, objectInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public static IScriptObject EvaluateNow(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator)
        {
            try
            {
                return EvaluateNow(s, variables, globalFunctionEvaluator, null);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public static IScriptObject EvaluateNow(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos)
        {
            try
            {
                ScriptEvaluator ee = new ScriptEvaluator();
                return ee.Evaluate(s, variables, globalFunctionEvaluator, objectInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        internal static IScriptObject EvaluateNow(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            try
            {
                ScriptEvaluator ee = new ScriptEvaluator();
                return ee.Evaluate(s, variables, globalFunctionEvaluator, objectInfos, scriptFunctionInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        internal static IScriptObject EvaluateNow(ScriptParser parser, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            try
            {
                ScriptEvaluator ee = new ScriptEvaluator();
                return ee.Evaluate(parser, variables, globalFunctionEvaluator, objectInfos, scriptFunctionInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public IScriptObject Evaluate(string s)
        {
            try
            {
                return Evaluate(s, null, null);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public IScriptObject Evaluate(string s, ScriptObjectTypeInfoCollection objectInfos)
        {
            try
            {
                return Evaluate(s, null, null, objectInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public IScriptObject Evaluate(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator)
        {
            try
            {
                return Evaluate(s, variables, globalFunctionEvaluator, null);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        public IScriptObject Evaluate(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos)
        {
            try
            {
                return Evaluate(s, variables, globalFunctionEvaluator, objectInfos, null);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        internal IScriptObject Evaluate(string s, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            try
            {
                return Evaluate(s, ScriptPosition.Empty, variables, globalFunctionEvaluator, objectInfos, scriptFunctionInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        internal IScriptObject Evaluate(string s, ScriptPosition position, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            try
            {
                return Evaluate(new ScriptParser(s, position), variables, globalFunctionEvaluator, objectInfos, scriptFunctionInfos);
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        internal IScriptObject Evaluate(ScriptParser parser, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            try
            {
                ScriptObjectTypeInfoCollection combinedObjectInfos = combineObjectInfos(objectInfos);

                IScriptObject result = evaluate(parser, variables, globalFunctionEvaluator, combinedObjectInfos, scriptFunctionInfos);
                return result;
            }
            catch (ScriptEvaluatorEndStatementException ex)
            {
                // An "end" statement was processed. Do not pass it up to the caller.
                return null;
            }
        }

        private IScriptObject evaluate(ScriptParser parser, ScriptVariableCollection variables, GlobalFunctionEvaluatorDelegate globalFunctionEvaluator, ScriptObjectTypeInfoCollection objectInfos, ScriptFunctionInfoCollection scriptFunctionInfos)
        {
            // We should only throw an exception of type ScriptEvaluatorException from here.

            if (parser.Script.Length == 0)
                return null;

            _parser = parser;
            _blockStartPosition = parser.Position; // assume we are entering at the start of the block
            if (variables == null)
                variables = new ScriptVariableCollection();
            _variables = variables;
            _globalFunctionEvaluator = globalFunctionEvaluator;
            _objectInfos = objectInfos;
            if (scriptFunctionInfos == null)
                _scriptFunctionInfos = ScriptFunctionInfoCollection.CreateFromScript(_parser.Script);
            else
                _scriptFunctionInfos = scriptFunctionInfos;
            _showDebug = false;
            _token = Token.Empty;

             _scriptObjectSponsorManager = new SponsorshipManager();

        start:

             try
             {
                 Token resultToken = evaluateBlock();
                 return resultToken.ScriptObj;
             }
             catch (ScriptEvaluatorBreakStatementException ex)
             {
                 throw new ScriptEvaluatorException("No enclosing loop out of which to break or continue.", ex.Position, null);
             }
             catch (ScriptEvaluatorContinueStatementException ex)
             {
                 throw new ScriptEvaluatorException("No enclosing loop out of which to break or continue.", ex.Position, null);
             }
             catch (ScriptEvaluatorReturnStatementException ex)
             {
                 return ex.ReturnValue;
             }
             catch (ScriptEvaluatorGotoStatementException ex)
             {
                 _parser.Position = findLabel(ex.GotoLabel);
                 if (_parser.Position.IsEmpty)
                     throw new ScriptEvaluatorException("Could not find label '" + ex.GotoLabel + "'", ex.Position, null);
                 GetToken();
                 goto start;
             }
             catch (ScriptEvaluatorEndStatementException ex)
             {
                 // An "end" statement was processed.
                 throw;
             }
             catch (ScriptEvaluatorException ex)
             {
                 throw; // just pass it on up.
             }
             catch (Exception ex)
             {
                 // The method invokes throw a TargetInvocationException which can wrap a ScriptEvaluatorEndStatementException, so check for that.
                 // For example the Debug.Asset statement can cause throw the end statement exception to cause the script to terminate.
                 if (ex.InnerException is ScriptEvaluatorEndStatementException)
                     throw ex.InnerException;

                 throw new ScriptEvaluatorException("An unknown error occurred while evaluating the script.", _token.Position, parser.IdentifyScriptPosition(_token.Position), ex);
             }
             finally
             {
                 _scriptObjectSponsorManager.UnregisterAll(); // this will not throw an exception
             }
        }



        private Token evaluateBlock()
        {
            bool forceExecuteMultipleLines = false; // TODO: is there a better way to determine if we are starting from a non-bracketed block, but still want to execute multiple lines?
            if (_parser.Position.PositionInScript == 0)
                forceExecuteMultipleLines = true;

            ScriptPosition blockStartPosition = _parser.Position;

            if (_token.IsEmpty) // if this is the 1st time in...
                GetToken();

            if (_token.IsEndOfScript)  // if the block is empty or if it just contained a comment then return.
                return Token.Empty;

            // Traditionally all variables declared in a {...} block are considered local to the block, however since we don't actually declare var we don't use this concept at this point in time.
            bool isBracketedBlock = false;
            if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "{")
                isBracketedBlock = true;

            Token resulttoken = Token.Empty;

            if (forceExecuteMultipleLines || isBracketedBlock)
            {
                if (isBracketedBlock)
                {
                    GetToken(); // get past {
                    if (_token.TokenString == "}") // check if it was an empty block like "{ }"
                        return Token.Empty;
                }

                resulttoken = Token.Empty;
                do
                {
                    if (!_token.IsEndOfStatement) // if the statement an empty-statement (just a ";") then skip it.
                    {
                        resulttoken = variableAssignment();
                        if (resulttoken.IsUndefinedIdentifier)
                            throw new ScriptEvaluatorException("Unknown literal '" + resulttoken.IdentifierName + "'.  If it is a function name, be sure to follow it with an open parenthesis '('.", resulttoken.Position, _parser.IdentifyScriptPosition(resulttoken.Position));
                        if (!(_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "}"))
                            ensureEndOfStatement();
                    }
                    GetToken(); // get past ; or }
                } while ((!_token.IsEndOfScript) && !(isBracketedBlock && _token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "}"));

                if (isBracketedBlock && !(_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "}"))
                    throw new ScriptEvaluatorException("Missing matching }.", blockStartPosition, _parser.IdentifyScriptPosition(blockStartPosition));
            }
            else
            {
                if (!_token.IsEndOfStatement) // if the statement an empty-statement (just a ";") then skip it.
                {
                    resulttoken = variableAssignment();
                    if (!(_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "}"))
                        ensureEndOfStatement();
                    if (resulttoken.IsUndefinedIdentifier)
                        throw new ScriptEvaluatorException("Unknown literal '" + resulttoken.IdentifierName + "'.  If it is a function name, be sure to follow it with an open parenthesis '('.", resulttoken.Position, _parser.IdentifyScriptPosition(resulttoken.Position));
                }
            }

            return resulttoken; // WE EXIT THIS FUNCTION with _token being a semi-colon or a closing bracket.
        }

        private void skipBlock()
        {
            // skips current line, or block if it starts with "{"
            // WHEN ENTERING this function, _token should be the 1st character of the block.

            // TODO: ?? if we wanted to check syntax of the block as well we could run it in a new temp context.

            ScriptPosition blockStartPosition = _token.Position;

            // Traditionally all variables declared in a {...} block are considered local to the block, however since we don't actually declare var we don't use this concept at this point in time.
            if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "{")
            {
                int count = 1;
                while (count != 0)
                {
                    GetToken();

                    if (_token.IsEndOfScript)
                        throw new ScriptEvaluatorException("Missing }.  End of script reached before matching } was found.", blockStartPosition, _parser.IdentifyScriptPosition(blockStartPosition));

                    if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "{")
                        count++;
                    else if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "}")
                        count--;
                }
            }
            else if (_token.TokenType == TokenTypeEnum.Keyword && (_token.TokenString.Equals("IF", StringComparison.OrdinalIgnoreCase) || _token.TokenString.Equals("WHILE", StringComparison.OrdinalIgnoreCase) || _token.TokenString.Equals("FOR", StringComparison.OrdinalIgnoreCase))) // skip if/while/for statements
            {
                GetToken(); // get "("
                if (_token.TokenString != "(")
                    throw new ScriptEvaluatorException("Missing '(' after 'if'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                int count = 1;
                while (count != 0)
                {
                    GetToken();

                    if (_token.IsEndOfScript)
                        throw new ScriptEvaluatorException("Missing ).  End of script reached before matching ) was found.", blockStartPosition, _parser.IdentifyScriptPosition(blockStartPosition));

                    if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                        count++;
                    else if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == ")")
                        count--;
                }

                GetToken(); // get 1st token of block since skipBlock() doesn't get it.
                skipBlock();
            }
            else if (_token.TokenType == TokenTypeEnum.Keyword && _token.TokenString.Equals("TRY", StringComparison.OrdinalIgnoreCase)) // skip try statement
            {
                GetToken(); // get "{"
                if (_token.TokenString != "{")
                    throw new ScriptEvaluatorException("Missing '{' after 'try'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                skipBlock(); // skip the try block.

                Token peekedToken = _parser.PeekToken(); // take a peek at the next token
                if (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.Equals("CATCH", StringComparison.OrdinalIgnoreCase))
                {
                    GetToken(); // get "catch"
                    GetToken(); // get "("
                    if (_token.TokenString != "(")
                        throw new ScriptEvaluatorException("Missing '(' after 'catch'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    int count = 1;
                    while (count != 0)
                    {
                        GetToken();

                        if (_token.IsEndOfScript)
                            throw new ScriptEvaluatorException("Missing ).  End of script reached before matching ) was found.", blockStartPosition, _parser.IdentifyScriptPosition(blockStartPosition));

                        if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                            count++;
                        else if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == ")")
                            count--;
                    }

                    GetToken(); // get "{"
                    if (_token.TokenString != "{")
                        throw new ScriptEvaluatorException("Missing '{' after 'catch' statement.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                    skipBlock();
                }

                peekedToken = _parser.PeekToken(); // take a peek at the next token
                if (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.Equals("FINALLY", StringComparison.OrdinalIgnoreCase))
                {
                    GetToken(); // get "finally"
                    GetToken(); // get "{"
                    if (_token.TokenString != "{")
                        throw new ScriptEvaluatorException("Missing '{' after 'finally'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position)); 
                    skipBlock();
                }
            }
            else // skip simple statement.
                while (!_token.IsEndOfStatement)
                {
                    if (_token.IsEndOfScript)
                        throw new ScriptEvaluatorException("End of script reached before statement was terminated.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                    GetToken();
                }


            // WE EXIT THIS FUNCTION with _token being a semi-colon or a closing bracket
        }

        ScriptPosition findLabel(string labelName)
        {
            // Scans the current block for the specified label name.

            ScriptPosition startPos = _parser.Position;
            Token startToken = _token;

            _parser.Position = _blockStartPosition;

            GetToken();
            Token prevToken = _token;
            GetToken();

            while (!_token.IsEndOfScript)
            {
                if (prevToken.TokenType == TokenTypeEnum.Identifier && _token.TokenString == ":")
                {
                    ScriptPosition result = _parser.Position;
                    _parser.Position = startPos;
                    _token = startToken;
                    return result;
                }
                prevToken = _token;
                GetToken();
            }

            _parser.Position = startPos;
            _token = startToken;

            return ScriptPosition.Empty;
        }

        private static ScriptObjectTypeInfoCollection _baseScriptObjectTypes;
        private readonly static object _baseScriptObjectTypes_load_lock = new object();
        private ScriptObjectTypeInfoCollection combineObjectInfos(ScriptObjectTypeInfoCollection objectInfos)
        {
            // Cache all the base types. These are types in the same namespace as IScriptObject and should always be available in the scripting language.
            lock (_baseScriptObjectTypes_load_lock)
            {
                if (_baseScriptObjectTypes == null)
                {
                    ScriptObjectTypeInfoCollection baseScriptObjectTypes = new ScriptObjectTypeInfoCollection();

                    Assembly assembly = typeof(IScriptObject).Assembly;

                    Type[] types = ScriptObject.GetScriptObjectTypes(false, true);
                    foreach (Type type in types)
                        baseScriptObjectTypes.Add(type, ScriptObject.GetShortTypeName(type));

                    _baseScriptObjectTypes = baseScriptObjectTypes;
                }
            }
            
            // Copy base script object infos.
            ScriptObjectTypeInfoCollection ois = new ScriptObjectTypeInfoCollection();
            foreach (ScriptObjectTypeInfo oi in _baseScriptObjectTypes)
                ois.Add(oi);

            // Add passed in object infos
            if (objectInfos != null)
                foreach (ScriptObjectTypeInfo oi in objectInfos)
                    if (!ois.Contains(oi.IdentifierName))
                        ois.Add(oi);

            return ois;
        }

        /// <summary>
        /// This is just a test function for testing the expression evaluator.
        /// </summary>
        public static void Test()
        {
            // Online Javascript Evaluator: http://www.mathe-online.at/JavaCalc/jcintro.html

            try
            {
                ScriptVariableCollection sv = new ScriptVariableCollection();

                sv["x"] = new ScriptNumber(1);
                sv["d"] = new ScriptDictionary();

                IScriptObject s1 = ScriptEvaluator.EvaluateNow("2 * ( 7 + 2 ); ", null, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 18);

                s1 = ScriptEvaluator.EvaluateNow("dict=new Dictionary(); return dict.Get(\"unknown\");", sv, null); //test null literal
                Debug.Assert(s1 == null);

                s1 = ScriptEvaluator.EvaluateNow("s=null; return (s==null);", sv, null); //test null literal
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("a1=null; a2=5; return (a1==a2);", sv, null); //test null literal
                Debug.Assert(s1.ToPrimitiveBoolean() == false);

                s1 = ScriptEvaluator.EvaluateNow("a1=null; a2=null; return (a1==a2);", sv, null); //test null literal
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("!(true);", null, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == false);

                s1 = ScriptEvaluator.EvaluateNow("  // this is a comment\r\n  ", null, null);
                Debug.Assert(s1 == null);

                s1 = ScriptEvaluator.EvaluateNow("7 != 2 == 1;", null, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == false);

                s1 = ScriptEvaluator.EvaluateNow("-(3);", null, null);
                Debug.Assert(s1.ToPrimitiveInt32() == -3);

                s1 = ScriptEvaluator.EvaluateNow("x = 4;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 4);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 4);


                s1 = ScriptEvaluator.EvaluateNow("x;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 4);

                s1 = ScriptEvaluator.EvaluateNow("x++;", sv, null); // returns x BEFORE incrementing.
                Debug.Assert(s1.ToPrimitiveInt32() == 4);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 5);

                s1 = ScriptEvaluator.EvaluateNow("x;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 5);

                s1 = ScriptEvaluator.EvaluateNow("number(\"11\") + 1;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 12);

                s1 = ScriptEvaluator.EvaluateNow("new DateTime().AddDays(1).ToString(\"MM/dd/yyyy\");", sv, null);
                //Debug.Assert(s1.ToPrimitiveDateTime() == ...);

                s1 = ScriptEvaluator.EvaluateNow("\"02/19/2008\" == new DateTime().AddDays(1).ToString(\"MM/dd/yyyy\");", sv, null);
                //Debug.Assert(s1.ToPrimitiveDateTime() == ...);

                s1 = ScriptEvaluator.EvaluateNow("new String(\"abc\").Length;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 3);

                s1 = ScriptEvaluator.EvaluateNow("1 << 2 == 4;", sv, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("-12;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == -12);

                s1 = ScriptEvaluator.EvaluateNow("!((-12) == -12);", sv, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == false);

                s1 = ScriptEvaluator.EvaluateNow("new Boolean(true);", sv, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("new Boolean(5);", sv, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("new Boolean(new DateTime());", sv, null);
                Debug.Assert(s1.ToPrimitiveBoolean() == true);

                s1 = ScriptEvaluator.EvaluateNow("new DateTime();", sv, null);
                s1 = ScriptEvaluator.EvaluateNow("new Dictionary();", sv, null);

                s1 = ScriptEvaluator.EvaluateNow("Math.Abs(-5);", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 5);

                s1 = ScriptEvaluator.EvaluateNow("Math.PI;", sv, null);
                Debug.Assert(s1.ToPrimitiveDouble() == Math.PI);

                s1 = ScriptEvaluator.EvaluateNow("if (true) x=1; else y=2; z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 1);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("\r\n \r\n if (true) x=1;\r\nelse\r\ny=2;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 1);

                s1 = ScriptEvaluator.EvaluateNow("if (true) { x=1; } else { y=2; } z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 1);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("x=0; while (x<2) x++; z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 2);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("x=0; while (x<1){ x++; continue; } z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 1);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("x=0; while (x<1){ x++; break; } z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 1);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("for (x=0; x<10; x++) { if (x>5) break; else continue; } z=0;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 6);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 0);

                s1 = ScriptEvaluator.EvaluateNow("x=0; myLabel: x++; if (x<2) goto myLabel;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 2);

                s1 = ScriptEvaluator.EvaluateNow("a=new Array();a[1]=4;x=a+2;a[2]=\"abc\";y=a[2]+2;", sv, null);
                Debug.Assert(((IScriptArray)sv["a"])[1].ToPrimitiveInt32() == 4);
                Debug.Assert(((IScriptArray)sv["a"])[2].ToPrimitiveString() == "abc");

                s1 = ScriptEvaluator.EvaluateNow("return add(2,3);  function Number add(Number a, Number b){ return a + b;} x=1;", sv, null);
                Debug.Assert(s1.ToPrimitiveInt32() == 5);

                s1 = ScriptEvaluator.EvaluateNow("function String concat(String a, String b){ return a + b;} function String concat(String a, Number b){ return a + String(b);} s1=concat(\"abc\", \"def\"); s2=concat(\"abc\", 456);", sv, null);
                Debug.Assert(sv["s1"].ToPrimitiveString() == "abcdef");
                Debug.Assert(sv["s2"].ToPrimitiveString() == "abc456");

                try
                {
                    s1 = ScriptEvaluator.EvaluateNow("function void innerEnd(){ end; } innerEnd(); return 12;", sv, null); // test running END statement within a function. It should not throw an exception to the calling applicaiton.
                    Debug.Assert(s1 == null);
                }
                catch (Exception ex) { Debug.Assert(true, "An exception should not have occurred!"); }

                s1 = ScriptEvaluator.EvaluateNow("; ; return \"hi\"; ;", null, null); // text consecutive semi-colons


                //s1 = ScriptEvaluator.EvaluateNow("Debug.Assert(true, \"blah\");", sv, null); // Should show a message box.  Selecting Yes or No should return null.
                //Debug.Assert(s1 == null);


                ScriptEvaluator.EvaluateNow("try { x=11; } catch (ex) { y=12; } z=5;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 11);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 5);

                ScriptEvaluator.EvaluateNow("try { xxxxx } catch (ex) { x=2; } finally { y = 3; } z=4;", sv, null);
                Debug.Assert(sv["x"].ToPrimitiveInt32() == 2);
                Debug.Assert(sv["y"].ToPrimitiveInt32() == 3);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 4);

                ScriptEvaluator.EvaluateNow("try { xxxxx } catch (ex) {} z=5;", sv, null);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 5);

                ScriptEvaluator.EvaluateNow("ex = 1; try { xxxxx } catch (ex) { x=2; msg=ex.message; } finally { y = 3; } z=4;", sv, null);
                Debug.Assert(sv["ex"].ToPrimitiveInt32() == 1); // this ensures that the ex variable persists outside of the catch block.

                ScriptEvaluator.EvaluateNow("z=5; try { try { xxxxx } catch (ex) { throw ex; } finally { y = 3; } z=4; } catch (ex) { msg123=ex.message; }", sv, null);
                Debug.Assert(sv["y"].ToPrimitiveInt32() == 3);
                Debug.Assert(sv["z"].ToPrimitiveInt32() == 5);
                Debug.Assert(sv.Contains("msg123"));

                // Test if/else skipBlock() logic.
                ScriptEvaluator.EvaluateNow("if (true) x = 1; else if (true) x = 2;", null, null);
                ScriptEvaluator.EvaluateNow("if (true) x = 1; else if (true) x = 2; else if (true) x = 3;", null, null);
                ScriptEvaluator.EvaluateNow("if (true) x = 1; else if (true) { x = 2; } else if (true) { x = 3; }", null, null);
                ScriptEvaluator.EvaluateNow("if (true) x = 1; else try { } catch (e) { } finally { }", null, null);
                ScriptEvaluator.EvaluateNow("if (true) x = 1; else while (true) break;", null, null);

                // Force Errors
                try
                {
                s1 = ScriptEvaluator.EvaluateNow("\"ab\"=1;", sv, null); // should error.
                s1 = ScriptEvaluator.EvaluateNow("Math.abc=1;", sv, null); // should error.
                s1 = ScriptEvaluator.EvaluateNow("break;", sv, null); // should error.
                s1 = ScriptEvaluator.EvaluateNow("if 1==2;", sv, null); // should error.
                s1 = ScriptEvaluator.EvaluateNow("{x=1;", sv, null); // should error.
                s1 = ScriptEvaluator.EvaluateNow("new Math()", sv, null); //this should fail since it's a static only class.
                s1 = ScriptEvaluator.EvaluateNow("number(\"11\", ;", sv, null); //this should fail since it's a static only class.
                s1 = ScriptEvaluator.EvaluateNow("function add(Number a, Number b){ return a + b;} x=1;", sv, null); // missing return type
                s1 = ScriptEvaluator.EvaluateNow("try { x=1xx } finally { y = 2; }", null, null); // no catch statement so the exception will be rethrown.
                s1 = ScriptEvaluator.EvaluateNow("try { x=1xx } catch (ex) { y=2xx; }", null, null); // there's an error in the catch block.
                s1 = ScriptEvaluator.EvaluateNow("Math.Abs(null);", sv, null); // should error
                }
                catch (Exception ex)
                {
                    // EXPECTED EXCEPTIONS.
                    System.Diagnostics.Debugger.Break();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debugger.Break();
            }

        }

        private Token variableAssignment()
        {
            // EBNC:  variableAssignment :== variable = logicalOr
            Token originalToken = _token;  // this is used when setting a static object property so we know the object name.

            Token leftval = logicalOr();

            if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "=")
            {
                // make sure the token has a name (ie, it is a variable)
                if (leftval.IdentifierName == null && leftval.ParentArrayObject == null)
                    throw new ScriptEvaluatorException("lvalue must be a variable for assignment.", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                Token prevToken = leftval;
                GetToken();
                Token rightval = logicalOr();

                ensureDefinedIfTokenIsIdentifier(rightval);

                rightval.IdentifierName = leftval.IdentifierName;
                rightval.TokenType = TokenTypeEnum.EvaluatedExpression;

                // If it's a variable name
                if (leftval.TokenString.IndexOf('.') == -1)  // TODO: There is currently nothing to indicate that the token is an variable or a variable property, so I just look for a '.' in the token string which is a less than desirable way to do it.
                {
                    if (leftval.IsArrayElement) // Its an array
                        leftval.ParentArrayObject[leftval.ParentArrayIndex] = rightval.ScriptObj;
                    else  // It's a variable
                        _variables[leftval.IdentifierName] = rightval.ScriptObj;
                }
                else // it is an object property
                {
                    if (leftval.ScriptObj == null)
                    {
                        // It's a static object (or it's an undefined object error)

                        // Check if the token is a known object type name
                        if (_objectInfos.Contains(originalToken.IdentifierName))
                        {
                            // Set the static object's property.
                            ScriptObjectTypeInfo oi = _objectInfos[originalToken.IdentifierName];
                            Type type = oi.Assembly.GetType(oi.FullTypeName);
                            PropertyInfo pi = type.GetProperty(leftval.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static);
                            if (pi == null)
                                throw new ScriptEvaluatorException(ScriptObjectInvokeResult.UndefinedMember, originalToken.IdentifierName, leftval.IdentifierName, originalToken.Position, _parser.IdentifyScriptPosition(originalToken.Position));
                            //if (pi.PropertyType != typeof(IScriptObject) && pi.PropertyType.IsSubclassOf(typeof(IScriptObject)) == false)
                            if (typeof(IScriptObject).IsAssignableFrom(pi.PropertyType) == false)
                                throw new ScriptEvaluatorException(ScriptObjectInvokeResult.InvalidType, originalToken.IdentifierName, leftval.IdentifierName, originalToken.Position, _parser.IdentifyScriptPosition(originalToken.Position));
                            if (pi.CanWrite == false)
                                throw new ScriptEvaluatorException(ScriptObjectInvokeResult.IsReadOnly, originalToken.IdentifierName, leftval.IdentifierName, originalToken.Position, _parser.IdentifyScriptPosition(originalToken.Position));

                            pi.SetValue(leftval.ScriptObj, rightval.ScriptObj, null);
                        }
                        else
                            throw new ScriptEvaluatorException(string.Format("'{0}' is not defined.", leftval.IdentifierName), leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                    }
                    else
                    {
                        // It's an instance object property
                        if (leftval.IsArrayElement) // Its an array
                        {
                            //leftval.ParentArrayObject[leftval.ParentArrayIndex] = rightval.ScriptObj; // WARNING: this calls ScriptArray[..], and not the actual object[..].
                            ScriptObjectInvokeResult setResult = leftval.ParentArrayObject.SetIndexer(leftval.ParentArrayIndex, rightval.ScriptObj);
                            if (setResult != ScriptObjectInvokeResult.Success)
                                throw new ScriptEvaluatorException(setResult, leftval.TokenString, "set_Item", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                        }
                        else
                        {
                            ScriptObjectInvokeResult setResult = leftval.ScriptObj.SetProperty(leftval.IdentifierName, rightval.ScriptObj);
                            if (setResult != ScriptObjectInvokeResult.Success)
                                throw new ScriptEvaluatorException(setResult, originalToken.TokenString, leftval.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                        }
                    }
                }

                leftval = rightval;

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }

            return leftval;
        }

        private Token logicalOr()
        {
            // EBNC:  logicalOr :== logicalAnd | value || logicalAnd { || logicalAnd }

            Token leftval = logicalAnd();

            while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "||")
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token theoperator = _token;
                Token prevToken = leftval;
                GetToken();
                Token rightval = logicalAnd();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptBoolean) || !(rightval.ScriptObj is ScriptBoolean))
                    throw new ScriptEvaluatorException("Operator '||' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                leftval = new Token(new ScriptBoolean(((ScriptBoolean)leftval.ScriptObj).ToPrimitiveBoolean() || ((ScriptBoolean)rightval.ScriptObj).ToPrimitiveBoolean()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token logicalAnd()
        {
            // EBNC:  logicalAnd :== bitwiseInclusiveOr | value && bitwiseInclusiveOr { && bitwiseInclusiveOr }

            Token leftval = bitwiseInclusiveOr();

            bool beenhere = false; // for short circuiting
            while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "&&")
            {
                Token theoperator = _token;
                Token prevToken = leftval;

                ensureDefinedIfTokenIsIdentifier(leftval);

                if (!(leftval.ScriptObj is ScriptBoolean))
                    throw new ScriptEvaluatorException("Operator '&&' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                if (beenhere & !((ScriptBoolean)leftval.ScriptObj).ToPrimitiveBoolean())
                {
                    // Short circuit... skip over the rest of the comparison expression and throw away those results.
                    while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "&&")
                    {
                        GetToken();
                        Token junkval = logicalOr();
                    }
                    break;
                }
                else
                    beenhere = true;
                GetToken();
                Token rightval = bitwiseAnd();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptBoolean) || !(rightval.ScriptObj is ScriptBoolean))
                    throw new ScriptEvaluatorException("Operator '&&' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                leftval = new Token(new ScriptBoolean(((ScriptBoolean)leftval.ScriptObj).ToPrimitiveBoolean() && ((ScriptBoolean)rightval.ScriptObj).ToPrimitiveBoolean()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token bitwiseInclusiveOr()
        {
            // EBNC:  bitwiseInclusiveOr :== bitwiseInclusiveOr | value | bitwiseInclusiveOr { | bitwiseInclusiveOr }

            Token leftval = bitwiseExclusiveOr();

            while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "|")
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token theoperator = _token;
                Token prevToken = leftval;
                GetToken();
                Token rightval = bitwiseExclusiveOr();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptNumber) || !(rightval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '|' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveInt32() | ((ScriptNumber)rightval.ScriptObj).ToPrimitiveInt32()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token bitwiseExclusiveOr()
        {
            // EBNC:  bitwiseExclusiveOr :== bitwiseExclusiveOr | value ^ bitwiseExclusiveOr { ^ bitwiseExclusiveOr }

            Token leftval = bitwiseAnd();

            while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "^")
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token theoperator = _token;
                Token prevToken = leftval;
                GetToken();
                Token rightval = bitwiseAnd();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptNumber) || !(rightval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '^' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveInt32() ^ ((ScriptNumber)rightval.ScriptObj).ToPrimitiveInt32()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token bitwiseAnd()
        {
            // EBNC:  bitwiseAnd :== equality | value & equality { & equality }

            Token leftval = equality();

            while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "&")
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token theoperator = _token;
                Token prevToken = leftval;
                GetToken();
                Token rightval = equality();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptNumber) || !(rightval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '&' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveInt32() & ((ScriptNumber)rightval.ScriptObj).ToPrimitiveInt32()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token equality()
        {
            // EBNC:  equality :== relational | value ( == | != ) relational { ( == | != ) relational }
            // If one value is a string and the other is not, the nonstring will be converted to a string before the compare.

            Token leftval = relational();

            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "==" || _token.TokenString == "!="))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token prevToken = leftval;
                Token theoperator = _token;
                GetToken();
                Token rightval = relational();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (theoperator.TokenString == "==")
                {
                    if (leftval.IsNull || rightval.IsNull)
                        leftval = new Token(new ScriptBoolean(leftval.IsNull && rightval.IsNull), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptBoolean && rightval.ScriptObj is ScriptBoolean)
                        leftval = new Token(new ScriptBoolean(((ScriptBoolean)leftval.ScriptObj).ToPrimitiveBoolean() == ((ScriptBoolean)rightval.ScriptObj).ToPrimitiveBoolean()), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptNumber && rightval.ScriptObj is ScriptNumber)
                        leftval = new Token(new ScriptBoolean(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() == ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                    else
                        leftval = new Token(new ScriptBoolean(leftval.ScriptObj.ToPrimitiveString() == rightval.ScriptObj.ToPrimitiveString()), TokenTypeEnum.EvaluatedExpression);
                }
                else // !=
                {
                    if (leftval.IsNull || rightval.IsNull)
                        leftval = new Token(new ScriptBoolean(!(leftval.IsNull && rightval.IsNull)), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptBoolean && rightval.ScriptObj is ScriptBoolean)
                        leftval = new Token(new ScriptBoolean(((ScriptBoolean)leftval.ScriptObj).ToPrimitiveBoolean() != ((ScriptBoolean)rightval.ScriptObj).ToPrimitiveBoolean()), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptNumber && rightval.ScriptObj is ScriptNumber)
                        leftval = new Token(new ScriptBoolean(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() != ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                    else
                        leftval = new Token(new ScriptBoolean(leftval.ScriptObj.ToPrimitiveString() != rightval.ScriptObj.ToPrimitiveString()), TokenTypeEnum.EvaluatedExpression);
                }

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token relational()
        {
            // EBNC:  relational :== bitwiseShift | value ( < | > | <= | >= ) bitwiseShift { ( < | > | <= | >= ) bitwiseShift )

            Token leftval = bitwiseShift();

            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "<" || _token.TokenString == ">" || _token.TokenString == "<=" || _token.TokenString == ">="))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token prevToken = leftval;
                Token theoperator = _token;
                GetToken();
                Token rightval = bitwiseShift();

                ensureDefinedIfTokenIsIdentifier(rightval);

                // Check for compatible types.
                // The types must not be null, must match, and must be Number, Date or String.
                Type leftType = leftval.ScriptObj == null ? null : leftval.ScriptObj.GetType();
                Type rightType = rightval.ScriptObj == null ? null : rightval.ScriptObj.GetType();

                if (
                    (leftType == null || rightType == null) ||
                    (leftType != rightType) ||
                    (leftType != typeof(ScriptNumber) && leftType != typeof(ScriptDateTime) && leftType != typeof(ScriptString)) ||
                    (rightType != typeof(ScriptNumber) && rightType != typeof(ScriptDateTime) && rightType != typeof(ScriptString))
                )
                    throw new ScriptEvaluatorException("Operator '" + theoperator + "' cannot be applied to operands of type '" + (leftval.ScriptObj != null ? leftval.ScriptObj.TypeName : "<null>") + "' and '" + (rightval.ScriptObj != null ? rightval.ScriptObj.TypeName : "<null>") + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                if (theoperator.TokenString == "<")
                {
                    if (leftType == typeof(ScriptNumber))
                        leftval = new Token(((ScriptNumber)leftval.ScriptObj) < ((ScriptNumber)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptDateTime))
                        leftval = new Token(((ScriptDateTime)leftval.ScriptObj) < ((ScriptDateTime)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptString))
                        leftval = new Token(((ScriptString)leftval.ScriptObj) < ((ScriptString)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                }
                else if (theoperator.TokenString == ">")
                {
                    if (leftType == typeof(ScriptNumber))
                        leftval = new Token(((ScriptNumber)leftval.ScriptObj) > ((ScriptNumber)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptDateTime))
                        leftval = new Token(((ScriptDateTime)leftval.ScriptObj) > ((ScriptDateTime)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptString))
                        leftval = new Token(((ScriptString)leftval.ScriptObj) > ((ScriptString)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                }
                else if (theoperator.TokenString == "<=")
                {
                    if (leftType == typeof(ScriptNumber))
                        leftval = new Token(((ScriptNumber)leftval.ScriptObj) <= ((ScriptNumber)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptDateTime))
                        leftval = new Token(((ScriptDateTime)leftval.ScriptObj) <= ((ScriptDateTime)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptString))
                        leftval = new Token(((ScriptString)leftval.ScriptObj) <= ((ScriptString)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                }
                else if (theoperator.TokenString == ">=")
                {
                    if (leftType == typeof(ScriptNumber))
                        leftval = new Token(((ScriptNumber)leftval.ScriptObj) >= ((ScriptNumber)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptDateTime))
                        leftval = new Token(((ScriptDateTime)leftval.ScriptObj) >= ((ScriptDateTime)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                    else if (leftType == typeof(ScriptString))
                        leftval = new Token(((ScriptString)leftval.ScriptObj) >= ((ScriptString)rightval.ScriptObj), TokenTypeEnum.EvaluatedExpression);
                }

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token bitwiseShift()
        {
            // EBNC:  bitwiseShift :== additive | value ( << | >> ) additive { ( << | >> ) additive }

            Token leftval = additive();

            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "<<" || _token.TokenString == ">>"))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token prevToken = leftval;
                Token theoperator = _token;
                GetToken();
                Token rightval = additive();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptNumber) || !(rightval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '&' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                // Since '<<' cannot be applied to numbers with a decimal point we currently just convert them to Int32.
                if (theoperator.TokenString == ">>")
                    leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveInt32() >> ((ScriptNumber)rightval.ScriptObj).ToPrimitiveInt32()), TokenTypeEnum.EvaluatedExpression);
                else
                    leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveInt32() << ((ScriptNumber)rightval.ScriptObj).ToPrimitiveInt32()), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token additive()
        {
            // EBNC:  additive :== multiplicative | value ( + | - ) multiplicative { ( + | - ) multiplicative }

            Token leftval = multiplicative();

            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "+" || _token.TokenString == "-"))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token prevToken = leftval;
                Token theoperator = _token;
                GetToken();
                Token rightval = multiplicative();

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (theoperator.TokenString == "+")
                {
                    // like javascript we convert arrays into a comma delimited string when using +.
                    if (leftval.ScriptObj is ScriptString || rightval.ScriptObj is ScriptString || leftval.ScriptObj is IScriptArray || rightval.ScriptObj is IScriptArray)
                        // String Concatenation
                        leftval = new Token(new ScriptString(leftval.ScriptObj.ToPrimitiveString() + rightval.ScriptObj.ToPrimitiveString()), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptNumber && rightval.ScriptObj is ScriptNumber)
                        // Numeric Addition
                        leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() + ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptDateTime && rightval.ScriptObj is ScriptTimeSpan)
                    {
                        // DateTime + TimeSpan = DateTime
                        DateTime dt = (DateTime)((ScriptDateTime)leftval.ScriptObj).ToPrimitiveObject();
                        TimeSpan ts = (TimeSpan)((ScriptTimeSpan)rightval.ScriptObj).ToPrimitiveObject();
                        leftval = new Token(new ScriptDateTime(dt + ts), TokenTypeEnum.EvaluatedExpression);
                    }
                    else if (leftval.ScriptObj is ScriptTimeSpan && rightval.ScriptObj is ScriptTimeSpan)
                    {
                        // TimeSpan + TimeSpan = TimeSpan
                        TimeSpan ts1 = (TimeSpan)((ScriptTimeSpan)leftval.ScriptObj).ToPrimitiveObject();
                        TimeSpan ts2 = (TimeSpan)((ScriptTimeSpan)rightval.ScriptObj).ToPrimitiveObject();
                        leftval = new Token(new ScriptTimeSpan(ts1 + ts2), TokenTypeEnum.EvaluatedExpression);
                    }
                    else
                        // Unsupported types for '+' operand
                        throw new ScriptEvaluatorException("The '+' operator can not be used on type '" + _token.TokenType.ToString() + "' and '" + rightval.TokenType.ToString() + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));
                }
                else
                {
                    if (leftval.ScriptObj is ScriptNumber && rightval.ScriptObj is ScriptNumber)
                        // Numeric Subtraction
                        leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() - ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                    else if (leftval.ScriptObj is ScriptDateTime && rightval.ScriptObj is ScriptDateTime)
                    {
                        // DateTime - DateTime = TimeSpan
                        DateTime dt1 = (DateTime)((ScriptDateTime)leftval.ScriptObj).ToPrimitiveObject();
                        DateTime dt2 = (DateTime)((ScriptDateTime)rightval.ScriptObj).ToPrimitiveObject();
                        leftval = new Token(new ScriptTimeSpan(dt1 - dt2), TokenTypeEnum.EvaluatedExpression);
                    }
                    else if (leftval.ScriptObj is ScriptDateTime && rightval.ScriptObj is ScriptTimeSpan)
                    {
                        // DateTime - TimeSpan = DateTime
                        DateTime dt = (DateTime)((ScriptDateTime)leftval.ScriptObj).ToPrimitiveObject();
                        TimeSpan ts = (TimeSpan)((ScriptTimeSpan)rightval.ScriptObj).ToPrimitiveObject();
                        leftval = new Token(new ScriptDateTime(dt - ts), TokenTypeEnum.EvaluatedExpression);
                    }
                    else if (leftval.ScriptObj is ScriptTimeSpan && rightval.ScriptObj is ScriptTimeSpan)
                    {
                        // TimeSpan - TimeSpan = TimeSpan
                        TimeSpan ts1 = (TimeSpan)((ScriptTimeSpan)leftval.ScriptObj).ToPrimitiveObject();
                        TimeSpan ts2 = (TimeSpan)((ScriptTimeSpan)rightval.ScriptObj).ToPrimitiveObject();
                        leftval = new Token(new ScriptTimeSpan(ts1 - ts2), TokenTypeEnum.EvaluatedExpression);
                    }
                    else
                        // Unsupported types for '-' operand
                        throw new ScriptEvaluatorException("The '-' operator can not be used on type '" + _token.TokenType.ToString() + "' and '" + rightval.TokenType.ToString() + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));
                }

                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token multiplicative()
        {
            // EBNC:  multiplicative :== factor | value ( * | / | %) factor { ( * | / | %) factor }

            Token leftval = factor();

            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "*" || _token.TokenString == "/" || _token.TokenString == "%"))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                Token theoperator = _token;
                GetToken();
                Token rightval = factor();
                Token prevToken = leftval;

                ensureDefinedIfTokenIsIdentifier(rightval);

                if (!(leftval.ScriptObj is ScriptNumber) || !(rightval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '" + theoperator + "' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "' and '" + rightval.ScriptObj.TypeName + "'", theoperator.Position, _parser.IdentifyScriptPosition(theoperator.Position));

                switch (theoperator.TokenString)
                {
                    case "*":
                        leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() * ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                        break;
                    case "/":
                        leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() / ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                        break;
                    case "%":
                        leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() % ((ScriptNumber)rightval.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                        break;
                }
                updateTokenStringAndPosition(ref leftval, prevToken, rightval);
            }
            return leftval;
        }

        private Token factor()
        {
            // EBNC:  factor :== [-...]( logicalOr ) | [-...]functionCall
            bool unaryNegation = false;
            bool logicalNegation = false;
            Token result;

            // TODO: currently we allow !-!-!- which makes no sense, so we may want to rewrite this later.
            // Determine the sign which the result should be due to any unary minuses
            Token entryToken = _token;
            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "-" || _token.TokenString == "!"))
            {
                if (_token.TokenString == "-")
                    unaryNegation = !unaryNegation;
                else
                    logicalNegation = !logicalNegation;
                GetToken();
            }

            if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
            {
                Token prevToken = _token;
                GetToken();
                result = logicalOr();
                updateTokenStringAndPosition(ref result, prevToken, _token);
                if (!_token.IsEndOfScript && _token.TokenString == ")")
                    GetToken();
                else
                    throw new ScriptEvaluatorException("missing ).", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
            }
            else
            {
                result = postIncrementDecrement();
            }

            // Set the sign of the result
            if (unaryNegation)
            {
                ensureDefinedIfTokenIsIdentifier(result);

                if (!(result.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '-' cannot be applied to an operand of type '" + result.TokenType + "'", entryToken.Position, _parser.IdentifyScriptPosition(entryToken.Position));
                Token unaryResult = new Token(new ScriptNumber(-((ScriptNumber)result.ScriptObj).ToPrimitiveDouble()), TokenTypeEnum.EvaluatedExpression);
                updateTokenStringAndPosition(ref unaryResult, entryToken, result);
                result = unaryResult;
            }
            if (logicalNegation)
            {
                ensureDefinedIfTokenIsIdentifier(result);

                if (!(result.ScriptObj is ScriptBoolean))
                    throw new ScriptEvaluatorException("Operator '!' cannot be applied to an operand of type '" + result.TokenType + "'", entryToken.Position, _parser.IdentifyScriptPosition(entryToken.Position));
                Token logicalNegationResult = new Token(new ScriptBoolean(!((ScriptBoolean)result.ScriptObj).ToPrimitiveBoolean()), TokenTypeEnum.EvaluatedExpression);
                updateTokenStringAndPosition(ref logicalNegationResult, entryToken, result);
                result = logicalNegationResult;
            }

            return result;
        }



        private Token postIncrementDecrement()
        {
            Token originalToken = _token;  // this is used just so we know the object name in case we need to create an exception.

            Token leftval = objectMemberAndArrayMember();

            if (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "++" || _token.TokenString == "--"))
            {
                ensureDefinedIfTokenIsIdentifier(leftval);

                if (leftval.IdentifierName == null)
                    throw new ScriptEvaluatorException("lvalue must be a variable for assignment.", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                Token prevToken = leftval;
                Token returnToken = leftval; // since we return the value before it was incremented

                // Get the Numeric Value token

                // TODO: need to support variables and array elements
                // If it's an instance object property name then get the property value token.
                if (prevToken.TokenString.Contains("."))  // TODO: There is currently nothing to indicate that the token is an variable or a variable property, so I just look for a '.' in the token string which is a less than desirable way to do it.
                {
                    // TODO: this does not support static properties, but we probably don't need to?

                    IScriptObject result;
                    ScriptObjectInvokeResult getResult = prevToken.ScriptObj.GetProperty(prevToken.IdentifierName, out result);
                    if (getResult == ScriptObjectInvokeResult.Success)
                    {
                        returnToken.ScriptObj = result;
                        _scriptObjectSponsorManager.Register(returnToken.ScriptObj); // Register a lifetime sponsor for the script object (it only does it if the object is remote) so that the object doesn't disappear while the script is still running.
                    }
                    else
                        throw new ScriptEvaluatorException(getResult, originalToken.TokenString, leftval.IdentifierName, originalToken.Position, _parser.IdentifyScriptPosition(originalToken.Position));

                    leftval = returnToken;
                }


                if (!(leftval.ScriptObj is ScriptNumber))
                    throw new ScriptEvaluatorException("Operator '" + _token.TokenString + "' cannot be applied to operands of type '" + leftval.ScriptObj.TypeName + "'.", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                // Do the ++/--
                if (_token.TokenString == "++")
                    leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() + 1), TokenTypeEnum.EvaluatedExpression);
                else
                    leftval = new Token(new ScriptNumber(((ScriptNumber)leftval.ScriptObj).ToPrimitiveDouble() - 1), TokenTypeEnum.EvaluatedExpression);

                updateTokenStringAndPosition(ref returnToken, prevToken, _token);
                returnToken.TokenType = TokenTypeEnum.EvaluatedExpression;


                // Store the variable/property
                if (prevToken.TokenString.Contains("."))  // TODO: There is currently nothing to indicate that the token is an variable or a variable property, so I just look for a '.' in the token string which is a less than desirable way to do it.
                {
                    // it is an object property

                    ScriptObjectInvokeResult setResult = prevToken.ScriptObj.SetProperty(prevToken.IdentifierName, leftval.ScriptObj);
                    if (setResult != ScriptObjectInvokeResult.Success)
                        throw new ScriptEvaluatorException(setResult, originalToken.TokenString, prevToken.IdentifierName, originalToken.Position, _parser.IdentifyScriptPosition(originalToken.Position));
                }
                else
                {
                    leftval.IdentifierName = prevToken.IdentifierName;
                    _variables[prevToken.IdentifierName] = leftval.ScriptObj;
                }
                leftval = returnToken; // Since the form "x++" returns the value of x before it was incremented.

                GetToken();
            }
            return leftval;
        }










        //private Token arrayElement()
        //{
        //    Token arrayToken = objectMember();

        //    while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "[")
        //    {
        //        // Make sure it's an array
        //        if (!(arrayToken.ScriptObj is ScriptArray))
        //            throw new ScriptEvaluatorException("Cannot apply indexing with [] to an expression of type '" + arrayToken.ScriptObj.TypeName + "'", _token.Position);

        //        Token theoperator = _token;
        //        GetToken();
        //        Token indexToken = logicalOr();

        //        if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "]")
        //            throw new ScriptEvaluatorException("Missing ']' after array index.", _token.Position);

        //        Token closeBracket = _token;
        //        GetToken();

        //        Token prevToken = arrayToken;

        //        int index = indexToken.ScriptObj.ToPrimitiveInt32();
        //        Token prevArrayToken = arrayToken;

        //        IScriptObject arrayElmentObject;
        //        ScriptObjectInvokeResult getResult = arrayToken.ScriptObj.GetIndexer(index, out arrayElmentObject);
        //        if (getResult != ScriptObjectInvokeResult.Success)
        //            throw new ScriptEvaluatorException(getResult, arrayToken.TokenString, "get_Item", arrayToken.Position);

        //        arrayToken = new Token((ScriptArray)arrayToken.ScriptObj, index, arrayElmentObject, TokenTypeEnum.EvaluatedExpression);

        //        //arrayToken = new Token((ScriptArray)arrayToken.ScriptObj, index, ((ScriptArray)arrayToken.ScriptObj)[index], TokenTypeEnum.EvaluatedExpression);
        //        updateTokenStringAndPosition(ref arrayToken, prevArrayToken, closeBracket);
        //    }
        //    return arrayToken;
        //}

        //private Token objectMember()
        //{
        //    Token leftval = newKeyword();

        //    // TODO: add code to also check that lefval is an object result. -- I'm not sure what I meant by this comment???
        //    while (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == ".")
        //    {
        //        Token prevToken = leftval;  // we need this to create the TokenString since we overwrite leftval below.
        //        GetToken(); // get the member name
        //        Token nameToken = _token;
        //        GetToken();

        //        if (nameToken.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
        //        {
        //            // It's a method.

        //            IScriptObject[] argarray = getFunctionParameters();

        //            if (leftval.ScriptObj == null)
        //            {
        //                // It's a static object (or it's an undefined object error)

        //                // Check if the token is a known object type name
        //                if (_objectInfos.Contains(leftval.IdentifierName))
        //                {
        //                    ScriptObjectTypeInfo oi = _objectInfos[leftval.IdentifierName];
        //                    Type type = oi.Assembly.GetType(oi.FullTypeName);
        //                    MethodInfo mi = type.GetMethod(nameToken.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static, null, Type.GetTypeArray(argarray), null);
        //                    if (mi == null)
        //                    {
        //                        mi = type.GetMethod(nameToken.IdentifierName);  // check if the method name exists at all (for any number of arguments).
        //                        if (mi == null)
        //                            throw new ScriptEvaluatorException(ScriptObjectInvokeResult.UndefinedMember, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);
        //                        else
        //                            throw new ScriptEvaluatorException(ScriptObjectInvokeResult.WrongNumberOfArguments, leftval.IdentifierName, nameToken.IdentifierName, argarray, leftval.Position);
        //                    }

        //                    if (mi.ReturnType != typeof(void) && !typeof(IScriptObject).IsAssignableFrom(mi.ReturnType))
        //                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.InvalidType, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);

        //                    // Check if the method has the [ScriptObjectMethodAttribute] flag.
        //                    if (mi.GetCustomAttributes(typeof(ScriptObjectMethodAttribute), false).Length == 0)
        //                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.MissingScriptMemberAttribute, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);

        //                    leftval.ScriptObj = (IScriptObject)mi.Invoke(null, argarray);
        //                }
        //                else
        //                    throw new ScriptEvaluatorException(string.Format("'{0}' is not defined.", leftval.IdentifierName), leftval.Position);
        //            }
        //            else
        //            {
        //                // It's an instance object method.
        //                ScriptObjectInvokeResult invokeResult = leftval.ScriptObj.InvokeMethod(nameToken.TokenString, argarray, out leftval.ScriptObj);
        //                if (invokeResult != ScriptObjectInvokeResult.Success)
        //                    throw new ScriptEvaluatorException(invokeResult, leftval.TokenString, nameToken.TokenString, argarray, leftval.Position);
        //            }
        //            updateTokenStringAndPosition(ref leftval, prevToken, _token);
        //            leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
        //            GetToken();  // Get the operator after the ")"
        //        }
        //        else if (nameToken.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "=" || _token.TokenString == "++" || _token.TokenString == "--"))
        //        {
        //            // it's a set property (lvalue)
        //            leftval = nameToken;
        //            leftval.ScriptObj = prevToken.ScriptObj;  // set the value equal to the containing object
        //            updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
        //            leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
        //        }
        //        else
        //        {
        //            // it's a get property (rvalue)
        //            if (leftval.ScriptObj == null)
        //            {
        //                // It's a static object (or it's an undefined object error)

        //                // Check if the token is a known object type name
        //                if (_objectInfos.Contains(leftval.IdentifierName))
        //                {
        //                    ScriptObjectTypeInfo oi = _objectInfos[leftval.IdentifierName];
        //                    Type type = oi.Assembly.GetType(oi.FullTypeName);
        //                    PropertyInfo pi = type.GetProperty(nameToken.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static );
        //                    if (pi == null)
        //                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.UndefinedMember, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);
        //                    if (pi.CanRead == false)
        //                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.IsWriteOnly, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);
        //                    //if (pi.PropertyType != typeof(IScriptObject) && pi.PropertyType.IsSubclassOf(typeof(IScriptObject)) == false)
        //                    if (typeof(IScriptObject).IsAssignableFrom(pi.PropertyType) == false)
        //                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.InvalidType, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position);
        //                    leftval.ScriptObj = (IScriptObject)pi.GetValue(null, null);
        //                    updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
        //                    leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
        //                }
        //                else
        //                    throw new ScriptEvaluatorException(string.Format("'{0}' is not defined.", leftval.IdentifierName), leftval.Position);
        //            }
        //            else
        //            {
        //                // It's an instance object property
        //                Token prevLeftVal = leftval;
        //                ScriptObjectInvokeResult getResult = leftval.ScriptObj.GetProperty(nameToken.IdentifierName, out leftval.ScriptObj);

        //                if (getResult != ScriptObjectInvokeResult.Success)
        //                    throw new ScriptEvaluatorException(getResult, leftval.TokenString, nameToken.TokenString, leftval.Position);

        //                updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
        //                leftval.IdentifierName = null;
        //                leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
        //            }
        //        }

        //        if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "[")
        //            return arrayElement();
        //    }

        //    return leftval;
        //}






        private Token objectMemberAndArrayMember()
        {
            // 6/22/2008 john Had to merge object member and array member code to get the following to work: blah.getmyarray()[0].mymember;
            Token leftval = newKeyword();

            // TODO: add code to also check that leftval is an object result. -- I'm not sure what I meant by this comment???
            while (_token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "." || _token.TokenString == "["))
            {
                if (_token.TokenString == ".")
                {
                    Token prevToken = leftval;  // we need this to create the TokenString since we overwrite leftval below.
                    GetToken(); // get the member name
                    Token nameToken = _token;
                    GetToken();

                    if (nameToken.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                    {
                        // It's a method.

                        IScriptObject[] argarray = getFunctionParameters();

                        if (leftval.ScriptObj == null)
                        {
                            // It's a static object (or it's an undefined object error)

                            // Check if the token is a known object type name
                            if (_objectInfos.Contains(leftval.IdentifierName))
                            {
                                ScriptObjectTypeInfo oi = _objectInfos[leftval.IdentifierName];
                                Type type = oi.Assembly.GetType(oi.FullTypeName);
                                //MethodInfo mi = type.GetMethod(nameToken.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static, null, Type.GetTypeArray(argarray), null);
                                MethodInfo mi = ScriptObject.GetMethodInfo(type, nameToken.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static, argarray);
                                if (mi == null)
                                {
                                    mi = type.GetMethod(nameToken.IdentifierName);  // check if the method name exists at all (for any number of arguments).
                                    if (mi == null)
                                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.UndefinedMember, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                                    else
                                        throw new ScriptEvaluatorException(ScriptObjectInvokeResult.WrongNumberOfArguments, leftval.IdentifierName, nameToken.IdentifierName, argarray, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                                }

                                if (mi.ReturnType != typeof(void) && !typeof(IScriptObject).IsAssignableFrom(mi.ReturnType))
                                    throw new ScriptEvaluatorException(ScriptObjectInvokeResult.InvalidType, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                                // Check if the method has the [ScriptObjectMethodAttribute] flag.
                                if (mi.GetCustomAttributes(typeof(ScriptObjectMethodAttribute), false).Length == 0)
                                    throw new ScriptEvaluatorException(ScriptObjectInvokeResult.MissingScriptMethodAttribute, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                                leftval.ScriptObj = (IScriptObject)mi.Invoke(null, argarray);
                                _scriptObjectSponsorManager.Register(leftval.ScriptObj); // Register a lifetime sponsor for the script object (it only does it if the object is remote) so that the object doesn't disappear while the script is still running.
                            }
                            else
                                throw new ScriptEvaluatorException(string.Format("'{0}' is not defined.", leftval.IdentifierName), leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                        }
                        else
                        {
                            // It's an instance object method.
                            IScriptObject result;
                            ScriptObjectInvokeResult invokeResult = leftval.ScriptObj.InvokeMethod(nameToken.TokenString, argarray, out result);
                            if (invokeResult == ScriptObjectInvokeResult.Success)
                            {
                                leftval.ScriptObj = result;
                                _scriptObjectSponsorManager.Register(leftval.ScriptObj); // Register a lifetime sponsor for the script object (it only does it if the object is remote) so that the object doesn't disappear while the script is still running.
                            }
                            else
                                throw new ScriptEvaluatorException(invokeResult, leftval.TokenString, nameToken.TokenString, argarray, nameToken.Position, _parser.IdentifyScriptPosition(nameToken.Position));

                        }
                        updateTokenStringAndPosition(ref leftval, prevToken, _token);
                        leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
                        GetToken();  // Get the operator after the ")"
                    }
                    else if (nameToken.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && (_token.TokenString == "=" || _token.TokenString == "++" || _token.TokenString == "--"))
                    {
                        // it's a set property (lvalue)
                        leftval = nameToken;
                        leftval.ScriptObj = prevToken.ScriptObj;  // set the value equal to the containing object
                        updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
                        leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
                    }
                    else
                    {
                        // it's a get property (rvalue)
                        if (leftval.ScriptObj == null)
                        {
                            // It's a static object (or it's an undefined object error)

                            // Check if the token is a known object type name
                            if (_objectInfos.Contains(leftval.IdentifierName))
                            {
                                ScriptObjectTypeInfo oi = _objectInfos[leftval.IdentifierName];
                                Type type = oi.Assembly.GetType(oi.FullTypeName);
                                // We don't need to loop though any object heirarchy since we are searching for a static property.
                                PropertyInfo pi = type.GetProperty(nameToken.IdentifierName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static);
                                if (pi == null)
                                    throw new ScriptEvaluatorException(ScriptObjectInvokeResult.UndefinedMember, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                                if (pi.CanRead == false)
                                    throw new ScriptEvaluatorException(ScriptObjectInvokeResult.IsWriteOnly, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                                //if (pi.PropertyType != typeof(IScriptObject) && pi.PropertyType.IsSubclassOf(typeof(IScriptObject)) == false)
                                if (typeof(IScriptObject).IsAssignableFrom(pi.PropertyType) == false)
                                    throw new ScriptEvaluatorException(ScriptObjectInvokeResult.InvalidType, leftval.IdentifierName, nameToken.IdentifierName, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                                leftval.ScriptObj = (IScriptObject)pi.GetValue(null, null);
                                _scriptObjectSponsorManager.Register(leftval.ScriptObj); // Register a lifetime sponsor for the script object (it only does it if the object is remote) so that the object doesn't disappear while the script is still running.
                                updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
                                leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
                            }
                            else
                                throw new ScriptEvaluatorException(string.Format("'{0}' is not defined.", leftval.IdentifierName), leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                        }
                        else
                        {
                            // It's an instance object property
                            Token prevLeftVal = leftval;
                            IScriptObject result;
                            ScriptObjectInvokeResult getResult = leftval.ScriptObj.GetProperty(nameToken.IdentifierName, out result);
                            if (getResult != ScriptObjectInvokeResult.Success)
                                throw new ScriptEvaluatorException(getResult, leftval.TokenString, nameToken.TokenString, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));
                            
                            leftval.ScriptObj = result;
                            _scriptObjectSponsorManager.Register(leftval.ScriptObj); // Register a lifetime sponsor for the script object (it only does it if the object is remote) so that the object doesn't disappear while the script is still running.

                            updateTokenStringAndPosition(ref leftval, prevToken, nameToken);
                            leftval.IdentifierName = null;
                            leftval.TokenType = TokenTypeEnum.EvaluatedExpression;
                        }
                    }
                }
                else
                {
                    Token arrayToken = leftval;

                    // Make sure it's an array
                    if (!(arrayToken.ScriptObj is IScriptArray))
                        throw new ScriptEvaluatorException("Cannot apply indexing with [] to an expression of type '" + arrayToken.ScriptObj.TypeName + "'", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    Token theoperator = _token;
                    GetToken();
                    Token indexToken = logicalOr();

                    ensureDefinedIfTokenIsIdentifier(indexToken);

                    if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "]")
                        throw new ScriptEvaluatorException("Missing ']' after array index.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    Token closeBracket = _token;
                    GetToken();

                    Token prevToken = arrayToken;

                    int index = indexToken.ScriptObj.ToPrimitiveInt32();
                    Token prevArrayToken = arrayToken;

                    IScriptObject arrayElmentObject;
                    ScriptObjectInvokeResult getResult = arrayToken.ScriptObj.GetIndexer(index, out arrayElmentObject);
                    if (getResult != ScriptObjectInvokeResult.Success)
                        throw new ScriptEvaluatorException(getResult, arrayToken.TokenString, "get_Item", arrayToken.Position, _parser.IdentifyScriptPosition(arrayToken.Position));

                    arrayToken = new Token((IScriptArray)arrayToken.ScriptObj, index, arrayElmentObject, TokenTypeEnum.EvaluatedExpression);

                    //arrayToken = new Token((ScriptArray)arrayToken.ScriptObj, index, ((ScriptArray)arrayToken.ScriptObj)[index], TokenTypeEnum.EvaluatedExpression);
                    updateTokenStringAndPosition(ref arrayToken, prevArrayToken, closeBracket);

                    leftval = arrayToken;
                }
            }

            return leftval;
        }















        private Token newKeyword()
        {
            Token leftval = tryKeyword();

            if (leftval.TokenType == TokenTypeEnum.Keyword && leftval.TokenString.ToUpper() == "NEW")
            {
                Token prevToken = leftval;
                Token identifierToken = _token;

                // Check if the token is a known object type name
                if (_objectInfos.Contains(identifierToken.TokenString))
                {
                    ScriptObjectTypeInfo oi = _objectInfos[identifierToken.TokenString];

                    GetToken();  // get the "("
                    if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                    {
                        IScriptObject[] argarray = getFunctionParameters();

                        object o;
                        try
                        {
                            o = oi.Assembly.CreateInstance(oi.FullTypeName, true, BindingFlags.Public | BindingFlags.Instance, null, argarray, null, null);
                        }
                        catch (MissingMethodException ex)
                        {
                            throw new ScriptEvaluatorException(string.Format("'{0}' does not have a public constructor matching the given arguments.", oi.IdentifierName), identifierToken.Position, _parser.IdentifyScriptPosition(identifierToken.Position));
                        }
                        catch (Exception ex)
                        {
                            throw new ScriptEvaluatorException(string.Format("An error occurred in the constructor of '{0}'.", oi.IdentifierName), identifierToken.Position, _parser.IdentifyScriptPosition(identifierToken.Position), ex);
                        }
                        if (o == null)
                            throw new ScriptEvaluatorException("Failed to instantiate '" + identifierToken.TokenString + " in assembly '" + oi.Assembly.FullName + "'", identifierToken.Position, _parser.IdentifyScriptPosition(identifierToken.Position));

                        leftval = new Token((IScriptObject)o, TokenTypeEnum.EvaluatedExpression);

                        updateTokenStringAndPosition(ref leftval, prevToken, _token);
                        GetToken();  // Get the operator after the ")"
                    }
                    else
                        throw new ScriptEvaluatorException("Missing '(' after type '" + identifierToken.TokenString + "'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                }
                else
                    throw new ScriptEvaluatorException("The object '" + identifierToken.TokenString + "' does not exist.", identifierToken.Position, _parser.IdentifyScriptPosition(identifierToken.Position));
            }
            return leftval;
        }


        private Token tryKeyword()
        {
            Token leftval = ifKeyword();

            if (leftval.TokenType == TokenTypeEnum.Keyword && leftval.TokenString.ToUpper() == "TRY")
            {
                if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "{")
                    throw new ScriptEvaluatorException("Missing '{' after 'try'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                ScriptPosition tryPosition = _token.Position;

                // Process the TRY block.
                Exception theException = null;
                try
                {
                    leftval = evaluateBlock();
                }
                catch (Exception ex)
                {
                    theException = ex;

                    // Since we didn't make it all the way through the try block, set our location back to the beginning of the try statement and skip the try section.
                    _parser.Position = tryPosition;
                    GetToken();  // get the "{" after the "try".
                    skipBlock();
                }


                bool foundCatchStatement = false;
                bool foundFinallyStatement = false;

                // Process the CATCH block.
                Token peekedToken = _parser.PeekToken(); // take a peek at the next token
                if (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.ToUpper() == "CATCH")
                {
                    foundCatchStatement = true;
                    GetToken(); // get the "CATCH" keyword.
                    GetToken(); // get the "(".
                    if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "(")
                        throw new ScriptEvaluatorException("Missing '(' after 'catch'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    GetToken(); // get the exception/error variable name
                    if (_token.TokenType != TokenTypeEnum.Identifier)
                        throw new ScriptEvaluatorException("Missing exception identifier in 'catch' statement.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                    string exceptionVariableName = _token.TokenString;
                    GetToken(); // get the ")".
                    if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != ")")
                        throw new ScriptEvaluatorException("Missing ')' after 'catch' exception identifier.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    GetToken(); // get the "{".
                    if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "{")
                        throw new ScriptEvaluatorException("Missing '{' after 'catch' statement.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    // Don't process the catch block if no exception occurred, or if the exception was a jump exception.
                    if (theException != null && !(theException is ScriptEvalutatorJumpStatementException))
                    {
                        // Remember the old variable that has the same name as the exception identifier (if there is one).
                        IScriptObject oldVar = null;
                        bool oldVarIsSet = false;
                        if (_variables.Contains(exceptionVariableName))
                        {
                            oldVar = _variables[exceptionVariableName];
                            oldVarIsSet = true;
                        }

                        // Set the exception variable.
                        _variables[exceptionVariableName] = new ScriptException(theException);

                        // Run catch block
                        try
                        {
                            leftval = evaluateBlock();
                        }
                        finally
                        {
                            // Remove the exception variable and put the original variable back if there was one.
                            _variables.Remove(exceptionVariableName);// the exception identifier is local to the catch block.
                            if (oldVarIsSet)
                                _variables[exceptionVariableName] = oldVar;
                        }
                    }
                    else
                    {
                        // skip the catch block
                        skipBlock();
                    }
                }

                // Process the FINALLY block.
                peekedToken = _parser.PeekToken(); // take a peek at the next token
                if (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.ToUpper() == "FINALLY")
                {
                    foundFinallyStatement = true;

                    GetToken(); // get the "FINALLY" keyword.
                    GetToken(); // get 1st token of block since skipBlock() doesn't get it.
                    if (_token.TokenType != TokenTypeEnum.Operator || _token.TokenString != "{")
                        throw new ScriptEvaluatorException("Missing '{' after 'finally'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                    // run finally block
                    leftval = evaluateBlock();
                }

                // Ensure there was a catch and/or finally statement.
                if (foundCatchStatement == false && foundFinallyStatement == false)
                    throw new ScriptEvaluatorException("Missing 'catch' or 'finally' statement after 'try' block.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

                // If we hit a jump statement then rethrow it.
                if (theException is ScriptEvalutatorJumpStatementException)
                    throw theException;

                // If there was no catch statement then rethrow the exception.
                if (foundCatchStatement == false && theException != null)
                    throw theException;

                return Token.Empty;
            }

            return leftval;
        }



        private Token ifKeyword()
        {
            Token leftval = whileKeyword();

            if (leftval.TokenType == TokenTypeEnum.Keyword && leftval.TokenString.ToUpper() == "IF")
            {
                if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                {
                    Token expression = variableAssignment();

                    if (!(expression.ScriptObj is ScriptBoolean))
                        throw new ScriptEvaluatorException("An if statement must result in a boolean, not a " + expression.ScriptObj.TypeName + " [" + expression.ScriptObj.ToPrimitiveString() + "]", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                    if (((ScriptBoolean)expression.ScriptObj).ToPrimitiveBoolean())
                    {
                        // The boolean condition evaluated to TRUE.
                        leftval = evaluateBlock();

                        // Skip any subsequent ELSE/ELSE IF blocks.
                        Token peekedToken = _parser.PeekToken(); // take a peek at the next token
                        while (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.ToUpper() == "ELSE")
                        {
                            GetToken(); // get the "else"
                            GetToken(); // get 1st token of block since skipBlock() doesn't get it.
                            skipBlock();
                            peekedToken = _parser.PeekToken(); // take a peek at the next token
                        }
                        return Token.Empty;
                    }
                    else
                    {
                        // The boolean condition evaluated to TRUE.
                        skipBlock();
                        Token peekedToken = _parser.PeekToken(); // take a peek at the next token
                        if (peekedToken.TokenType == TokenTypeEnum.Keyword && peekedToken.TokenString.ToUpper() == "ELSE")
                        {
                            GetToken(); // get past the ;
                            GetToken(); // get past the "else"
                            leftval = evaluateBlock();
                            return Token.Empty;
                        }
                    }

                }
                else
                    throw new ScriptEvaluatorException("Missing '(' after 'if'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
            }
            return leftval;
        }

        private Token whileKeyword()
        {
            Token leftval = forKeyword();


            if (leftval.TokenType == TokenTypeEnum.Keyword && leftval.TokenString.ToUpper() == "WHILE")
            {
                if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                {
                    ScriptPosition whileConditionPosition = _token.Position;

                whileLoop:

                    Token expression = variableAssignment();

                    ScriptPosition whileBlockPosition = _token.Position;

                    if (!(expression.ScriptObj is ScriptBoolean))
                        throw new ScriptEvaluatorException("A while statement must result in a boolean, not a " + expression.ScriptObj.TypeName + " [" + expression.ScriptObj.ToPrimitiveString() + "]", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                    if (((ScriptBoolean)expression.ScriptObj).ToPrimitiveBoolean())
                    {
                        try
                        {
                            leftval = evaluateBlock();
                        }
                        catch (ScriptEvaluatorBreakStatementException)
                        {
                            _parser.Position = whileBlockPosition;  // jump back to beginning of block
                            GetToken(); // get 1st token of block since skipBlock() doesn't get it.
                            skipBlock(); // and then skip the whole block;
                            return Token.Empty;
                        }
                        catch (ScriptEvaluatorContinueStatementException)
                        {
                            // do nothing, just go back to the beginning of the loop which is done below.
                        }

                        // set our location back to where we were
                        _parser.Position = whileConditionPosition;
                        GetToken();  // get the "(" after the "while".
                        goto whileLoop;
                    }
                    else
                    {
                        skipBlock();
                        return Token.Empty;
                    }

                }
                else
                    throw new ScriptEvaluatorException("Missing '(' after 'while'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
            }
            return leftval;
        }

        private Token forKeyword()
        {
            Token leftval = jumpStatementKeyword();

            //for (int i = 0; i < 1; i++)
            //    i++;
            if (leftval.TokenType == TokenTypeEnum.Keyword && leftval.TokenString.ToUpper() == "FOR")
            {
                if (_token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
                {
                    GetToken(); // get past (

                    Token initExpression = variableAssignment();

                    if (!_token.IsEndOfStatement)
                        throw new ScriptEvaluatorException("Missing ; after 'for' init expression.", leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                    GetToken(); // get past ;

                    // Strip out the condition expression
                    ScriptPosition forConditionPosition = _token.Position;
                    while (!_token.IsEndOfStatement)
                    {
                        if (_token.IsEndOfScript)
                            throw new ScriptEvaluatorException("End of script reached before statement was terminated.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                        GetToken();
                    }
                    string strConditionExpression = _parser.Substring(forConditionPosition, _parser.Position);

                    GetToken(); // get past ;

                    // Strip out the increment expression
                    ScriptPosition forIncrementPosition = _token.Position;
                    while (_token.TokenString != ")")
                    {
                        if (_token.IsEndOfScript)
                            throw new ScriptEvaluatorException("End of script reached before statement was terminated.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                        GetToken();
                    }
                    string strIncrementExpression = _parser.Substring(forIncrementPosition, _parser.Position.PositionInScript - forIncrementPosition.PositionInScript - 1) + ";";

                    GetToken(); // get past )

                    ScriptPosition forBlockPosition = _token.Position;

                    // Evaluate the condition
                    IScriptObject conditionResult = ScriptEvaluator.EvaluateNow(strConditionExpression, _variables, _globalFunctionEvaluator, _objectInfos, _scriptFunctionInfos);

                    if (!(conditionResult is ScriptBoolean))
                        throw new ScriptEvaluatorException("A 'for' condition statement must result in a boolean, not a " + conditionResult.TypeName + " [" + conditionResult.ToString() + "]", forConditionPosition, _parser.IdentifyScriptPosition(forConditionPosition));

                    while (((ScriptBoolean)conditionResult).ToPrimitiveBoolean())
                    {
                        try
                        {
                            leftval = evaluateBlock();
                        }
                        catch (ScriptEvaluatorBreakStatementException)
                        {
                            _parser.Position = forBlockPosition;  // jump back to beginning of block
                            GetToken(); // get 1st token of block since skipBlock() doesn't get it.
                            skipBlock(); // and then skip the whole block;
                            return Token.Empty;
                        }
                        catch (ScriptEvaluatorContinueStatementException)
                        {
                            // do nothing, just go back to the beginning of the loop which is done below.
                        }


                        IScriptObject incrementResult = ScriptEvaluator.EvaluateNow(strIncrementExpression, _variables, _globalFunctionEvaluator, _objectInfos, _scriptFunctionInfos);

                        conditionResult = ScriptEvaluator.EvaluateNow(strConditionExpression, _variables, _globalFunctionEvaluator, _objectInfos, _scriptFunctionInfos);

                        _parser.Position = forBlockPosition;
                        GetToken();  // get the 1st token in the for block.
                    }

                    // Skip over for block
                    skipBlock();
                    return Token.Empty;
                }
                else
                    throw new ScriptEvaluatorException("Missing '(' after 'for'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
            }
            return leftval;
        }

        private Token jumpStatementKeyword()
        {
            Token leftval = functionCall();


            if (leftval.TokenType == TokenTypeEnum.Keyword)
            {
                switch (leftval.TokenString.ToUpper())
                {
                    case "RETURN":
                        if (!_token.IsEndOfStatement) // is there a return value specified?
                        {
                            Token returnValue = variableAssignment();
                            ensureEndOfStatement();
                            throw new ScriptEvaluatorReturnStatementException(returnValue.ScriptObj, leftval.Position);
                        }
                        else
                        {
                            ensureEndOfStatement();
                            throw new ScriptEvaluatorReturnStatementException(null, leftval.Position);
                        }
                    case "CONTINUE":
                        ensureEndOfStatement();
                        throw new ScriptEvaluatorContinueStatementException(leftval.Position);
                    case "BREAK":
                        ensureEndOfStatement();
                        throw new ScriptEvaluatorBreakStatementException(leftval.Position);
                    case "END":
                        ensureEndOfStatement();
                        throw new ScriptEvaluatorEndStatementException(leftval.Position);
                    case "GOTO":
                        if (_token.TokenType != TokenTypeEnum.Identifier)
                            throw new ScriptEvaluatorException(string.Format("Invalid 'goto' label '{0}'.", _token.TokenString), _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                        GetToken();
                        ensureEndOfStatement();
                        throw new ScriptEvaluatorGotoStatementException(_token.TokenString, leftval.Position);

                    case "THROW":
                        Token exceptionToken = variableAssignment();
                        ScriptException scriptException = exceptionToken.ScriptObj as ScriptException;
                        if (scriptException == null)
                            throw new ScriptEvaluatorException("The throw statement must throw an object of type Exception.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
                        ensureEndOfStatement();
                        throw new ScriptEvaluatorException(scriptException.Exception.Message, leftval.Position, _parser.IdentifyScriptPosition(leftval.Position));

                    case "FUNCTION":
                        // This isn't really a jump statement so it probably doesn't really belong in this function, but we do jump over the function block.
                        ScriptPosition functionDeclarePos = _token.Position;
                        // The preprocessor already validated the function declaration syntax, so just skip until we get to the block start '{'.
                        while (_token.TokenString != "{" & !_token.IsEndOfScript)
                            GetToken();
                        if (_token.TokenString != "{")
                            throw new ScriptEvaluatorException("Missing '{' after 'function' declaration.", functionDeclarePos, _parser.IdentifyScriptPosition(functionDeclarePos));
                        // Skip over the function block.
                        skipBlock();
                        leftval = Token.Empty;
                        break;
                }
            }

            return leftval;
        }

        private Token functionCall()
        {
            // functionCall :== value | NOW( format , offset ) | YYYYMMDD( logicalOr ) | UCASE( logicalOr )
            Token token = _token;
            GetToken();


            // Is it a label?  If so skip over it.
            while (token.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && _token.TokenString == ":")
            {
                GetToken();
                token = _token;
                GetToken();
            }



            // Is it a global function...
            if (token.TokenType == TokenTypeEnum.Identifier && _token.TokenType == TokenTypeEnum.Operator && _token.TokenString == "(")
            {
                Token prevToken = token;
                IScriptObject[] argarray = getFunctionParameters();

                // Try scripted functions
                if (_scriptFunctionInfos != null && _scriptFunctionInfos.ContainsFunctionOverride(token.IdentifierName, argarray))
                {
                    try
                    {
                        ScriptFunctionInfo functionInfo = _scriptFunctionInfos.GetByNameAndArgTypes(token.IdentifierName, argarray);

                        // Add arguments as variables
                        ScriptVariableCollection oldVars = new ScriptVariableCollection();
                        for (int i = 0; i < functionInfo.Arguments.Count; i++)
                        {
                            string argumentName = functionInfo.Arguments.GetKeyAt(i);
                            // If there is already a variable with the same name then remember it's value since it goes out of scope when exiting the function.
                            if (_variables.Contains(argumentName))
                                oldVars[argumentName] = _variables[argumentName];

                            // Add/Replace argument as a variable
                            _variables[argumentName] = argarray[i];
                        }

                        ScriptPosition position = _parser.Position;

                        _parser.Position = functionInfo.BlockStartPosition;

                        // Call the private evaluate() method so that it will throw an exception if an end statement is run within the function.
                        ScriptEvaluator ee = new ScriptEvaluator();
                        IScriptObject result = ee.evaluate(_parser, _variables, _globalFunctionEvaluator, _objectInfos, _scriptFunctionInfos);

                        // Make sure the return type matches.                        
                        if (functionInfo.ReturnTypeName.Equals("void", StringComparison.OrdinalIgnoreCase))
                            result = null; // ensure we don't return anything if the function type is void.
                        else if (string.Compare(result.TypeName, functionInfo.ReturnTypeName, true) != 0)
                            throw new ScriptEvaluatorException("Could not implicitly convert return type of '" + result.TypeName + "' to the function's declared return type of '" + functionInfo.ReturnTypeName + "'.");

                        _parser.Position = position;

                        // Remove arguments from variables. 
                        for (int i = 0; i < functionInfo.Arguments.Count; i++)
                            _variables.Remove(functionInfo.Arguments.GetKeyAt(i));

                        // Add old duplicate variables back in.
                        _variables.AddRange(oldVars);

                        if (result == null)
                            token = Token.Empty;
                        else
                            token = new Token(result, TokenTypeEnum.EvaluatedExpression);
                        updateTokenStringAndPosition(ref token, prevToken, _token);
                        GetToken();  // Get the operator after the ")"
                    }
                    catch (Exception ex)
                    {
                        throw;
                    }
                }

                else
                {






                    IScriptObject resultScriptObject;
                    // Try built in functions.
                    ScriptObjectInvokeResult functionResult = builtInFunctions(token.IdentifierName, argarray, token.Position, out resultScriptObject);  // see if it's a built in scripting function
                    if (functionResult != ScriptObjectInvokeResult.Success)
                    {
                        if (_globalFunctionEvaluator != null)
                        {
                            // Try the developer defined functions.
                            ScriptObjectInvokeResult functionResult2 = _globalFunctionEvaluator(token.IdentifierName, argarray, out resultScriptObject);  // see if it's a built in scripting function
                            if (functionResult2 != ScriptObjectInvokeResult.Success)
                                // just use the original functionResult value for the exception
                                throw new ScriptEvaluatorException(functionResult, "Global", token.IdentifierName, argarray, token.Position, _parser.IdentifyScriptPosition(token.Position));
                        }
                        else
                            throw new ScriptEvaluatorException(functionResult, "Global", token.IdentifierName, argarray, token.Position, _parser.IdentifyScriptPosition(token.Position));

                    }

                    if (resultScriptObject == null)
                        token = Token.Empty;
                    else
                        token = new Token(resultScriptObject, TokenTypeEnum.EvaluatedExpression);

                    updateTokenStringAndPosition(ref token, prevToken, _token);
                    GetToken();  // Get the operator after the ")"
                }
            }
            return token;
        }

        private IScriptObject[] getFunctionParameters()
        {
            // evaluate function delegate is not required.
            //if (_globalFunctionEvaluator == null)
            //	throw new ScriptEvaluatorException("The evaluate function delegate has not been set to evaluate " + _globalFunctionEvaluator + "(...).");

            ArrayList arguments = new ArrayList();

            GetToken(); // get past the '('

            // 4/1/2007 john Fixed infinite loop problem if the the end of line or end of statement was reached before the ')'.
            while (_token.TokenString != ")" && !_token.IsEndOfScript && !_token.IsEndOfStatement)  // while the token is not the ")" operator
            {
                Token arg = variableAssignment();

                ensureDefinedIfTokenIsIdentifier(arg);

                arguments.Add(arg.ScriptObj);
                if (_token.TokenString == ",") // get past comma delimiter
                    GetToken();
            }

            if (_token.TokenString != ")") // was the trailing ")" missing?
                throw new ScriptEvaluatorException("Function or method is missing ')'.", _token.Position, _parser.IdentifyScriptPosition(_token.Position));

            // put arguments in a string array
            IScriptObject[] argarray = new IScriptObject[arguments.Count];
            arguments.CopyTo(argarray, 0);

            return argarray;
        }

        private void ensureEndOfStatement()
        {
            if (!_token.IsEndOfStatement)
                throw new ScriptEvaluatorException("; expected", _token.Position, _parser.IdentifyScriptPosition(_token.Position));
        }

        private void ensureDefinedIfTokenIsIdentifier(Token token)
        {
            // If the specified token is an identifier and is undefined then throw an error.
            if (token.IsUndefinedIdentifier)
                throw new ScriptEvaluatorException("The name '" + token.IdentifierName + "' does not exist in the current context.", token.Position, _parser.IdentifyScriptPosition(token.Position));
        }

        private void updateTokenStringAndPosition(ref Token tokenToUpdate, Token firstTokenInOperation, Token lastTokenInOperation)
        {
            int length;
            if (lastTokenInOperation.IsEmpty)
                length = _parser.ScriptLength - firstTokenInOperation.Position.PositionInScript;
            else
                length = lastTokenInOperation.Position.PositionInScript + lastTokenInOperation.TokenString.Length - firstTokenInOperation.Position.PositionInScript;
            tokenToUpdate.TokenString = _parser.Substring(firstTokenInOperation.Position, length);
            tokenToUpdate.Position = firstTokenInOperation.Position;

            if (_showDebug)
                System.Diagnostics.Trace.WriteLine(tokenToUpdate.TokenString + " --> " + tokenToUpdate.ScriptObj.ToPrimitiveString());
        }


        private void GetToken()
        {
            _token = _parser.GetToken();

            // lookup variable
            if (_token.TokenType == TokenTypeEnum.Identifier)
            {
                if (_variables.Contains(_token.TokenString))
                    _token = new Token(_token.TokenString, _variables[_token.TokenString], TokenTypeEnum.Identifier, _token.Position, _token.TokenString);
                else
                    _token.IsUndefinedIdentifier = true;
            }
        }


        private ScriptObjectInvokeResult builtInFunctions(string functionname, IScriptObject[] args, ScriptPosition position, out IScriptObject result)
        {
            // Return null if nothing to return.

            result = null;

            switch (functionname.ToUpper())
            {
                #region Convert Functions
                case "STRING":
                    if (args.Length != 1)
                        return ScriptObjectInvokeResult.WrongNumberOfArguments;
                    try
                    {
                        result = new ScriptString(args[0]);
                    }
                    catch (Exception ex)
                    {
                        throw new ScriptEvaluatorException("Could not convert '" + args[0].ToPrimitiveString() + "' to string.", position, _parser.IdentifyScriptPosition(position), ex);
                    }
                    return ScriptObjectInvokeResult.Success;
                case "BOOLEAN":
                    if (args.Length != 1)
                        return ScriptObjectInvokeResult.WrongNumberOfArguments;
                    try
                    {
                        result = new ScriptBoolean(args[0].ToPrimitiveBoolean());
                    }
                    catch (Exception ex)
                    {
                        throw new ScriptEvaluatorException("Could not convert '" + args[0].ToPrimitiveString() + "' to boolean.", position, _parser.IdentifyScriptPosition(position), ex);
                    }
                    return ScriptObjectInvokeResult.Success;
                case "NUMBER":
                    if (args.Length != 1)
                        return ScriptObjectInvokeResult.WrongNumberOfArguments;
                    try
                    {
                        result = new ScriptNumber(args[0].ToPrimitiveDouble());
                    }
                    catch (Exception ex)
                    {
                        throw new ScriptEvaluatorException("Could not convert '" + args[0].ToPrimitiveString() + "' value to number.", position, _parser.IdentifyScriptPosition(position), ex);
                    }
                    return ScriptObjectInvokeResult.Success;
                #endregion

                default:
                    return ScriptObjectInvokeResult.UndefinedMember;
            }
        }

        /// <summary>
        /// Determines whether the specified System.Type implements the specified interface System.Type.
        /// This differs from Type.IsAssignableFrom() in that it returns false if theType and interfaceType are the same type.
        /// </summary>
        /// <param name="theType">The type to compare.</param>
        /// <param name="interfaceType">The interface type which theType must implement to return true.</param>
        /// <returns>true is theType implements interfaceType.</returns>
        private bool isTypeImplementationOf(Type theType, Type interfaceType)
        {
            // The method is largely based on the internal .net method Type.ImplementInterface().

            if (interfaceType.IsInterface == false)
                throw new ArgumentException("ifaceType must be an interface type.");

            for (Type type = theType; type != null; type = type.BaseType) // this for loop seems unncessary but I use it since Type.ImplementInterface() uses it.
            {
                Type[] interfaces = type.GetInterfaces();
                if (interfaces != null)
                {
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        if ((interfaces[i] == interfaceType) || ((interfaces[i] != null) && isTypeImplementationOf(interfaces[i], interfaceType)))
                            return true;
                    }
                }
            }
            return false;
        }
    }
}
